package net.nothinglost.renotification;

/*
    Copyright (C) 2011, Carl Reinke
    Copyright (C) 2011, Jeff Moyer <phro@alum.wpi.edu>
    
    This file is part of Renotification.

    Renotification is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Renotification is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Renotification.  If not, see <http://www.gnu.org/licenses/>.
 */

import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.IBinder;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.provider.CallLog.Calls;
import android.util.Log;
import java.util.HashSet;
import java.util.Set;

/*
 * TODO: allow more flexible notification frequency
 * TODO: add in a battery state monitor, and turn off the notifications if battery level too low.
 * TODO: google voice support (maybe) -- https://www.google.com/voice/inbox/recent/unread/
 */

public class NotificationService extends Service
{
    public static final String ACTIVATE_ACTION = "net.nothinglost.renotification.ACTIVATE";
    public static final String DISMISS_ACTION = "net.nothinglost.renotification.DISMISS";
    
    private static final String ALARM_ACTION = "net.nothinglost.renotification.ALARM";
    
    /* amount of time screen was on before notification arrived for device to be considered already 'active' */
    private final int SCREEN_ON_THRESHOLD_NANOS = 1000 * 1000 * 1000; // 1 second
    
    private PendingIntent alarmIntent;
    
    /* state information */
    private boolean isNotifierRunning;
    private long screenOnNanoTime;
    
    /* preferences */
    private SharedPreferences preferences;
    
    private SharedPreferences.OnSharedPreferenceChangeListener preferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener()
    {
        public void onSharedPreferenceChanged( SharedPreferences prefs, String key )
        {
            loadPreferences();
        }
    };
    
    private int notifierIntervalMillis;
    
    /* notification sources */
    private Set<String> activeSourceSet = new HashSet<String>();
    
    /* notification sink flags */
    private boolean vibrateSinkEnabled;
    private Uri ringtoneSinkUri;
    
    /* dismissal methods */
    private boolean screenOnDismissalEnabled;
    private boolean screenCycleDismissalEnabled;
    private boolean screenUnlockDismissalEnabled;
    
    /* amount of time between screen-on and screen-off to be considered a cycle */
    private final long SCREEN_CYCLE_NANOS = 1500 * 1000 * 1000;  // 1.5 seconds
    
    /* system services */
    private AlarmManager alarmManager;
    private NotificationManager notificationManager;
    private PowerManager powerManager;
    
    /* notification IDs */
    final private int NOTIFIER = 1;
    
    private BroadcastReceiver updateReceiver = new BroadcastReceiver()
    {
        @Override
        public void onReceive( Context context, Intent intent )
        {
            final String action = intent.getAction();
            
            if (action.equals(ACTIVATE_ACTION))
            {
                final String source = intent.getStringExtra("source");
                final boolean active = intent.getBooleanExtra("active", false);
                
                if (active)
                {
                    final boolean allowScreenOn = intent.getBooleanExtra("allowScreenOn", false);
                    
                    // do nothing if device was already on (unless allowed)
                    if (!allowScreenOn && screenWasOn())
                    {
                        Log.d(getString(R.string.TAG), "not activating source '" + source + "'; screen was already on");
                    }
                    else
                    {
                        Log.d(getString(R.string.TAG), "activating source '" + source + "'");

                        activeSourceSet.add(source);
                        startNotifier();
                    }
                }
                else
                {
                    Log.d(getString(R.string.TAG), "deactivating source '" + source + "'");
                    
                    activeSourceSet.remove(source);
                    if (activeSourceSet.isEmpty())
                        stopNotifier();
                }
            }
            else if (action.equals(DISMISS_ACTION))
            {
                activeSourceSet.clear();
                stopNotifier();
            }
            else if (action.equals(ALARM_ACTION))
            {
                runNotifier();
            }
        }
    };

    private BroadcastReceiver unblankReceiver = new BroadcastReceiver()
    {
        @Override
        public void onReceive( Context context, Intent intent )
        {
            final String action = intent.getAction();
            
            if (action.equals(Intent.ACTION_SCREEN_ON))
            {
                screenOnNanoTime = System.nanoTime();
                
                if (isNotifierRunning && screenOnDismissalEnabled)
                {
                    Log.d(getString(R.string.TAG), "caught screen-on dismissal");
                    
                    sendDismissAction();
                }
            }
            else if (action.equals(Intent.ACTION_SCREEN_OFF))
            {
                if (isNotifierRunning && screenCycleDismissalEnabled)
                {
                    long screenCycleNanoCount = System.nanoTime() - screenOnNanoTime;
                    if (screenCycleNanoCount < SCREEN_CYCLE_NANOS)
                    {
                        Log.d(getString(R.string.TAG), "caught screen-cycle dismissal");
                        
                        sendDismissAction();
                    }
                }
            }
            else if (action.equals(Intent.ACTION_USER_PRESENT))
            {
                if (isNotifierRunning && screenUnlockDismissalEnabled)
                {
                    Log.d(getString(R.string.TAG), "caught screen-unlock dismissal");
                    
                    sendDismissAction();
                }
            }
        }
    };
    
    private ContentObserver missedCallsObserver = new ContentObserver(null)
    {
        @Override
        public void onChange( boolean selfChange )
        {
            if (!preferences.getBoolean(getString(R.string.missed_call_source_enabled_key), true))
                return;

            Cursor cursor = getContentResolver().query(
                                Calls.CONTENT_URI,
                                null,
                                Calls.TYPE + " = ? AND " + Calls.NEW + " = ?", 
                                new String[] { Integer.toString(Calls.MISSED_TYPE), "1" },
                                null
                            );
            
            Log.d(getString(R.string.TAG), "observed " + cursor.getCount() + " missed calls");
            
            sendActivateAction(getClass().getName(), cursor.getCount() > 0, true);
        }
    };

    @Override
    public void onCreate()
    {
        Log.d(getString(R.string.TAG), "service created");

        alarmIntent = PendingIntent.getBroadcast(this, 0, new Intent(ALARM_ACTION), 0);
        
        /* preferences */
        preferences = PreferenceManager.getDefaultSharedPreferences(this);
        
        loadPreferences();
        
        preferences.registerOnSharedPreferenceChangeListener(preferencesListener);
        
        /* get system services */
        alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);

        /* register broadcast receivers */
        
        IntentFilter updateFilter = new IntentFilter();
        updateFilter.addAction(ACTIVATE_ACTION);
        updateFilter.addAction(DISMISS_ACTION);
        updateFilter.addAction(ALARM_ACTION);
        registerReceiver(updateReceiver, updateFilter);

        IntentFilter unblankFilter = new IntentFilter();
        unblankFilter.addAction(Intent.ACTION_SCREEN_ON);
        unblankFilter.addAction(Intent.ACTION_SCREEN_OFF);
        unblankFilter.addAction(Intent.ACTION_USER_PRESENT);
        registerReceiver(unblankReceiver, unblankFilter);

        /* register missed call observer */
        getApplicationContext().getContentResolver().registerContentObserver(Calls.CONTENT_URI, true, missedCallsObserver);
        
        /* initial state */
        
        screenOnNanoTime = System.nanoTime();

        /* service doesn't know of anything to notify about yet */
        isNotifierRunning = false;
    }

    @Override
    public void onDestroy()
    {
        Log.d(getString(R.string.TAG), "service destroyed");

        stopNotifier();
        
        /* preferences */
        preferences.unregisterOnSharedPreferenceChangeListener(preferencesListener);

        /* unregister broadcast recievers */
        unregisterReceiver(updateReceiver);
        unregisterReceiver(unblankReceiver);
        
        /* unregister missed call observer */
        getApplicationContext().getContentResolver().unregisterContentObserver(missedCallsObserver);
    }

    @Override
    public IBinder onBind( Intent intent )
    {
        return null;
    }

    @Override
    public int onStartCommand( Intent intent, int flags, int startId )
    {
        Log.d(getString(R.string.TAG), "service started");

        if (intent != null)
        {
            final String action = intent.getAction();

            if (action != null)
            {
                if (action.equals(ACTIVATE_ACTION) ||
                    action.equals(DISMISS_ACTION) ||
                    action.equals(ALARM_ACTION))
                {
                    updateReceiver.onReceive(this, intent);
                }
            }
        }
        
        return START_STICKY;
    }

    private void loadPreferences()
    {
        if (!preferences.getBoolean(getString(R.string.service_enabled_key), true))
            stopSelf();
        
        try
        {
            notifierIntervalMillis = Integer.parseInt(preferences.getString(getString(R.string.notification_frequency_key), "60")) * 1000;
        }
        catch (NumberFormatException e)
        {
            notifierIntervalMillis = 60 * 1000;
        }
        
        vibrateSinkEnabled = preferences.getBoolean(getString(R.string.vibrate_sink_enabled_key), false);
        
        String ringtoneSinkString = preferences.getString(getString(R.string.notification_ringtone_sink_key), "");
        ringtoneSinkUri = !ringtoneSinkString.equals("") ? Uri.parse(ringtoneSinkString) : null;
        
        screenOnDismissalEnabled = preferences.getBoolean(getString(R.string.screen_on_dismissal_enabled_key), false);
        screenCycleDismissalEnabled = preferences.getBoolean(getString(R.string.screen_cycle_dismissal_enabled_key), false);
        screenUnlockDismissalEnabled = preferences.getBoolean(getString(R.string.screen_unlock_dismissal_enabled_key), false);
    }

    private void sendActivateAction( String source, boolean active, boolean allowScreenOn )
    {
        Intent intent = new Intent();
        intent.setAction(NotificationService.ACTIVATE_ACTION);
        intent.putExtra("source", source);
        intent.putExtra("active", active);
        intent.putExtra("allowScreenOn", allowScreenOn);

        updateReceiver.onReceive(this, intent);
    }
    
    private void sendDismissAction()
    {
        Intent intent = new Intent();
        intent.setAction(NotificationService.DISMISS_ACTION);

        updateReceiver.onReceive(this, intent);
    }
    
    private void startNotifier()
    {
        // do nothing if notification already running
        if (isNotifierRunning)
            Log.d(getString(R.string.TAG), "starting notifier, but notifier already started");
        else
            Log.d(getString(R.string.TAG), "starting notifier; notifier set to run every " + notifierIntervalMillis + " ms");
        
        isNotifierRunning = true;

        alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + notifierIntervalMillis, notifierIntervalMillis, alarmIntent);
    }

    private void stopNotifier()
    {
        // do nothing if notification not active
        if (!isNotifierRunning)
            Log.d(getString(R.string.TAG), "stopping notifier, but notifier not started");
        else
            Log.d(getString(R.string.TAG), "stopping notifier");
        
        isNotifierRunning = false;

        alarmManager.cancel(alarmIntent);
        
        notificationManager.cancel(NOTIFIER);
    }

    private void runNotifier()
    {
        Log.d(getString(R.string.TAG), "running notifier");

        int icon = R.drawable.notification;
        long when = System.currentTimeMillis();

        Notification notification = new Notification(icon, null, when);

        Context context = getApplicationContext();
        CharSequence contentTitle = getString(R.string.notification_title);
        CharSequence contentText = getString(R.string.notification_text);
        PendingIntent contentIntent = PendingIntent.getBroadcast(context, 0, new Intent(DISMISS_ACTION), 0);

        notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);        

        /* clearing all notifications should also dismiss notifier */
        notification.deleteIntent = contentIntent;

        if (ringtoneSinkUri != null)
            notification.sound = ringtoneSinkUri;

        if (vibrateSinkEnabled)
            notification.defaults |= Notification.DEFAULT_VIBRATE;

        notificationManager.notify(NOTIFIER, notification);
    }
    
    private boolean screenWasOn()
    {
        long screenOnNanoCount = System.nanoTime() - screenOnNanoTime;
        
        return (powerManager.isScreenOn() && screenOnNanoCount > SCREEN_ON_THRESHOLD_NANOS);
    }
}
